package com.york.jobmew.auth.domain.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 封装RedisUtil工具类
 */
@Component
@Slf4j
public class RedisUtil {

    /**
     * 根据配置得到的加强版的redisTemplate
     * 内置我们自己的序列化规则
     */
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 全局设定缓存key的分割符
     */
    private static final String CACHE_KEY_SEPARATOR = ".";

    /**
     * 用户签到记录的 Redis key 前缀
     */
    private static final String USER_SIGN_IN_REDIS_KEY_PREFIX = "auth.user.signins";

    /**
     * 获取用户签到记录的 Redis Key
     *
     * @param year   年份
     * @param userId 用户 id
     * @return 拼接好的 Redis Key
     */
    public String buildUserSignInKey(int year, long userId) {
        return buildKey(USER_SIGN_IN_REDIS_KEY_PREFIX, String.valueOf(year), String.valueOf(userId));
    }

    /**
     * 构建缓存key,其实就String的拼接,用Stream合并即可
     */
    public String buildKey(String... strObjs) {
        return Stream.of(strObjs).collect(Collectors.joining(CACHE_KEY_SEPARATOR));
    }

    /**
     * 是否存在key
     */
    public boolean exist(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 删除key
     */
    public boolean del(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * set(不带过期)
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * setNx(带过期)
     * 可以做分布式锁,双重检查
     */
    public boolean setNx(String key, String value, Long time, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit);
    }

    /**
     * 获取string类型缓存
     */
    public String get(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * zset带有score的set做排行榜
     *
     * @param key
     * @param value
     * @param score
     * @return
     */
    public Boolean zAdd(String key, String value, Long score) {
        return redisTemplate.opsForZSet().add(key, value, Double.valueOf(String.valueOf(score)));
    }

    /**
     * 计算zset总数,做排行榜
     *
     * @param key
     * @return
     */
    public Long countZset(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 对指定区间的成员根据分数进行排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> rangeZset(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 删除
     *
     * @param key
     * @param value
     * @return
     */
    public Long removeZset(String key, Object value) {
        return redisTemplate.opsForZSet().remove(key, value);
    }

    /**
     * 删除一系列
     *
     * @param key
     * @param value
     */
    public void removeZsetList(String key, Set<String> value) {
        value.stream().forEach((val) -> redisTemplate.opsForZSet().remove(key, val));
    }

    /**
     * 计算在zset的score
     *
     * @param key
     * @param value
     * @return
     */
    public Double score(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    public Set<String> rangeByScore(String key, long start, long end) {
        return redisTemplate.opsForZSet().rangeByScore(key, Double.valueOf(String.valueOf(start)), Double.valueOf(String.valueOf(end)));
    }

    public Object addScore(String key, Object obj, double score) {
        return redisTemplate.opsForZSet().incrementScore(key, obj, score);
    }

    public Object rank(String key, Object obj) {
        return redisTemplate.opsForZSet().rank(key, obj);
    }

    public Set<ZSetOperations.TypedTuple<String>> rankWithScore(String key, long start, long end) {
        Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        return set;
    }

    public void putHash(String key, String hashKey, Object hashVal) {
        redisTemplate.opsForHash().put(key, hashKey, hashVal);
    }

    public Integer getInt(String key) {
        return (Integer) redisTemplate.opsForValue().get(key);
    }

    public void increment(String key, Integer count) {
        redisTemplate.opsForValue().increment(key, count);
    }

    public Map<Object, Object> getHashAndDelete(String key) {
        Map<Object, Object> map = new HashMap<>();
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, ScanOptions.NONE);
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> entry = cursor.next();
            Object hashKey = entry.getKey();
            Object value = entry.getValue();
            map.put(hashKey, value);
            redisTemplate.opsForHash().delete(key, hashKey);
        }
        return map;
    }


    /**
     * 设置BitMap中某一位的值
     *
     * @param key    Redis key
     * @param offset 位偏移量（从0开始）
     * @param value  true=1, false=0
     */
    public void setBit(String key, long offset, boolean value) {
        redisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 获取BitMap中某一位的值
     *
     * @param key    Redis key
     * @param offset 位偏移量
     * @return true=1, false=0
     */
    public boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 获取BitMap中所有设置为1的位偏移量（核心方法）
     *
     * @param key Redis key
     * @return 偏移量列表（从0开始）
     */
    public List<Long> getBitSet(String key) {
        byte[] bytes = (byte[]) redisTemplate.execute((RedisCallback<byte[]>) connection ->
                connection.get(key.getBytes(StandardCharsets.UTF_8))
        );

        if (bytes == null) return Collections.emptyList();

        BitSet bitSet = BitSet.valueOf(bytes);
        List<Long> indices = new ArrayList<>();
        for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
            indices.add((long) i);
        }
        return indices;
    }

    /**
     * 统计BitMap中设置为1的位数
     *
     * @param key Redis key
     * @return 1的个数
     */
    public Long bitCount(String key) {
        return (Long) redisTemplate.execute((RedisCallback<Long>) connection ->
                connection.bitCount(key.getBytes(StandardCharsets.UTF_8))
        );
    }
}